import ast
import sys
import os
import difflib
import tokenize
from io import StringIO
import pandas as pd
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
from collections import defaultdict
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QPushButton, QFileDialog, QTextEdit,
    QLabel, QMessageBox, QGridLayout, QGroupBox, QHBoxLayout
)
from PySide6.QtCore import QThread, Signal
import re

# 设置字体
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False 

class Worker(QThread):
    """
    工作线程类，继承自QThread，用于执行异步任务。

    本类主要用于需要在后台执行的操作，以避免长时间运行的任务阻塞主线程，
    从而保持应用程序的响应性。通过重写QThread的run方法来定义线程执行的具体逻辑。
    """
    output_signal = Signal(str)

    def __init__(self, folder_path, selected_function):
        super().__init__()
        self.folder_path = folder_path
        self.selected_function = selected_function

    def run(self):
        if self.selected_function == "similarity":
            self.main_similarity_main(self.folder_path)
        elif self.selected_function == "duplication":
            self.main_duplicate_code(self.folder_path)
        elif self.selected_function == "segment_duplication":
            self.main_segment_duplicate(self.folder_path)

    def main_similarity_main(self, folder_path):
        """
        主函数，用于计算指定文件夹中所有Python文件的相似度，并输出结果。

        参数:
        folder_path (str): 输入文件夹的路径，该文件夹包含需要进行相似度比较的Python文件。

        返回:
        无
        """
        # 创建输出结果文件夹
        output_folder = os.path.join(folder_path, '输出结果')
        os.makedirs(output_folder, exist_ok=True)

        # 获取文件夹中所有Python文件的路径
        file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith('.py')]
        
        # 初始化字典，用于存储变量名、函数名、类名的出现次数和文件名
        variable_dict = defaultdict(lambda: defaultdict(int))
        function_dict = defaultdict(lambda: defaultdict(int))
        class_dict = defaultdict(lambda: defaultdict(int))
        # 初始化字典，用于存储序列化的AST
        serialized_ast_dict = {}

        # 遍历所有Python文件，提取信息并计算相似度
        for file_path in file_paths:
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    source = file.read()
                    # 提取变量名、函数名和类名
                    variables = self.extract_variable_names(source)
                    functions, classes = self.extract_function_and_class_names(source)

                    # 更新字典中变量名、函数名、类名的出现次数和文件名
                    for var in variables:
                        variable_dict[var][os.path.basename(file_path)] += 1
                    for func in functions:
                        function_dict[func][os.path.basename(file_path)] += 1
                    for cls in classes:
                        class_dict[cls][os.path.basename(file_path)] += 1

                    # 序列化AST并存储
                    serialized_ast_dict[os.path.basename(file_path)] = self.serialize_ast(source)

            except (SyntaxError, FileNotFoundError) as e:
                # 发送错误信号
                self.output_signal.emit(f"处理文件 {file_path} 时出错: {e}")

        # 准备计算相似度矩阵所需的数据
        file_names = list(serialized_ast_dict.keys())
        serialized_asts = list(serialized_ast_dict.values())
        # 发送开始计算相似度的信号
        self.output_signal.emit(f"正在执行相似度计算...")
        # 计算相似度矩阵
        similarity_matrix = self.calculate_similarity(serialized_asts)

        # 将相似度矩阵输出到Excel文件
        output_file = os.path.join(output_folder, '相似度结果.xlsx')
        df = pd.DataFrame(similarity_matrix, index=file_names, columns=file_names)
        df.to_excel(output_file)
        # 发送输出成功的信号
        self.output_signal.emit(f"输出相似度结果到 Excel 文件成功！")

        # 将重复的变量名、函数名、类名输出到TXT文件
        with open(os.path.join(output_folder, '重复项结果.txt'), 'w', encoding='utf-8') as result_file:
            # 输出重复的变量名
            result_file.write("重复的变量名:\n")
            result_file.write(f"{'变量名':<20}{'出现次数':<15}{'出现文件'}\n")
            result_file.write("=" * 50 + "\n")
            
            for var, files in variable_dict.items():
                if len(files) > 1:  # 只输出在多个文件中出现的变量
                    count = sum(files.values())
                    file_list = ', '.join([f"{file}({count})" for file, count in files.items()])
                    result_file.write(f"{var:<20}{count:<15}{file_list}\n")

            # 输出重复的函数名
            result_file.write("\n重复的函数名:\n")
            result_file.write(f"{'函数名':<20}{'出现次数':<15}{'出现文件'}\n")
            result_file.write("=" * 50 + "\n")

            for func, files in function_dict.items():
                if len(files) > 1:  # 只输出在多个文件中出现的函数
                    count = sum(files.values())
                    file_list = ', '.join([f"{file}({count})" for file, count in files.items()])
                    result_file.write(f"{func:<20}{count:<15}{file_list}\n")

            # 输出重复的类名
            result_file.write("\n重复的类名:\n")
            result_file.write(f"{'类名':<20}{'出现次数':<15}{'出现文件'}\n")
            result_file.write("=" * 50 + "\n")

            for cls, files in class_dict.items():
                if len(files) > 1:  # 只输出在多个文件中出现的类
                    count = sum(files.values())
                    file_list = ', '.join([f"{file}({count})" for file, count in files.items()])
                    result_file.write(f"{cls:<20}{count:<15}{file_list}\n")
            
            # 发送输出成功的信号
            self.output_signal.emit(f"输出重复项结果到 TXT 文件成功！")

        # 可视化相似度矩阵并保存为图片
        self.visualize_similarity_matrix(df, os.path.join(output_folder, '相似度热图.png'))
        self.output_signal.emit(f"相似度热图已保存到 {os.path.join(output_folder, '相似度热图.png')}")
        self.output_signal.emit("执行相似度检测完成，相似度结果为代码结构的相似性统计！")
    def main_duplicate_code(self, folder_path):
        """
        主函数，用于检测给定文件夹中Python代码文件的重复率。

        :param folder_path: 待检测的文件夹路径
        """
        # 创建输出结果文件夹
        output_folder = os.path.join(folder_path, '输出结果')
        os.makedirs(output_folder, exist_ok=True)

        # 获取文件夹中所有.py文件的路径
        file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith('.py')]
        
        # 初始化存储清理后代码的字典和文件名列表
        cleaned_codes = {}
        file_names = [os.path.splitext(os.path.basename(file))[0] for file in file_paths]

        # 遍历所有文件，进行格式化处理并去除注释和文档字符串
        for file_path in file_paths:
            self.output_signal.emit(f"格式化处理文件: {os.path.basename(file_path)}")
            source = self.read_file(file_path)
            cleaned_code = self.remove_comments_and_docstrings(source)
            cleaned_codes[file_path] = cleaned_code

        # 计算所有文件之间的代码相似度
        self.output_signal.emit("开始计算相似度...")
        similarity_matrix = [[0] * len(file_names) for _ in range(len(file_names))]

        for i in range(len(file_paths)):
            for j in range(len(file_paths)):
                if i != j:
                    similarity = self.calculate_code_similarity(cleaned_codes[file_paths[i]], cleaned_codes[file_paths[j]])
                    similarity_matrix[i][j] = similarity * 100

        # 将相似度矩阵输出到Excel文件
        df = pd.DataFrame(similarity_matrix, columns=file_names, index=file_names)
        output_excel_file = os.path.join(output_folder, '代码重复率.xlsx')
        df.to_excel(output_excel_file, index=True)

        self.output_signal.emit(f'代码重复率数据已成功输出到 {output_excel_file}')

        # 输出相似代码片段到文本文件
        with open(os.path.join(output_folder, '重复代码片段.txt'), 'w', encoding='utf-8') as f:
            self.output_signal.emit(f'正在打印相似代码片段到重复代码片段.txt中...')
            f.write("相似代码片段:\n")
            for i in range(len(file_paths)):
                file1 = file_paths[i]
                for j in range(len(file_paths)):
                    if i != j:
                        file2 = file_paths[j]
                        similar_code = self.extract_similar_code(cleaned_codes[file1], cleaned_codes[file2])
                        if similar_code:
                            f.write(f"\n——— {os.path.basename(file1)} 与 {os.path.basename(file2)} 的相似代码片段 ———\n")
                            f.write(similar_code + "\n")

        self.output_signal.emit('重复代码片段已输出到重复代码片段.txt中。')
        self.output_signal.emit("执行重复度检测完成，重复度结果为格式化后的代码重复率统计！")

    def main_segment_duplicate(self, folder_path):
        """
        主函数，用于检测给定文件夹中Python文件的重复代码段。
        
        :param folder_path: 待检测的文件夹路径
        """
        # 创建输出结果文件夹
        output_folder = os.path.join(folder_path, '输出结果')
        os.makedirs(output_folder, exist_ok=True)

        # 初始化字典存储文件内容
        name_dir = {}
        # 定义排除列表，包含不参与重复检测的关键词和字符
        excl = ['utf-8', 'input', 'tuinfomgr.txt', 'for', 'student', 'print', 'break', 'flag', 
                'choice', 'True', 'False', 'if', 'elif', 'stuinfomgr', 'select', 'round', 'os.path.exists',
                'score', 'else', 'continue', 'while', 'def', 'return', 'with', 'as', 'open',
                'file', 'read', 'readlines', 'write', 'writelines', 'close', 'start', 'system',
                'os', 'rmdir', 'remove', 'exit', 'exit_program', 'main', 'work_directory', 'name', 'inp',
                'student_info_file', 'create_directory', 'create_student_info_file', 'encoding', 'txt', '.py',
                'split', 'lambda', '---', '===', '   ', ' = i', 'item in', 'except', 'import', 'in', 'range', 'true', 'Python', 'char', '==']
        # 设定重复代码段的最小长度
        min_num = 7

        # 遍历 .py 文件
        file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith('.py')]
        
        for file_path in file_paths:
            filename = os.path.basename(file_path)
            content = self.read_file(file_path)
            name_dir[filename] = content

        # 查找重复代码段
        duplicates = self.find_duplicates(name_dir, min_num, excl)

        # 输出结果到文件
        with open(os.path.join(output_folder, 'check_files.txt'), 'w', encoding='utf-8') as file:
            self.output_signal.emit(f'正在打印输出结果到check_files.txt中...')
            for target_filename, result in duplicates.items():
                file.write(f"------------------------------------------------------------\n")
                file.write(f"{target_filename}\n")
                if result['duplicates']:
                    for name_list, obj_list in result['duplicates'].items():
                        file.write(f"存在的雷同文件：{name_list} \n")
                        for segment in obj_list:
                            file.write(f"       雷同片段：{segment}\n")
                else:
                    file.write("没有与该文件重复的文件\n")
                file.write("\n")

        self.output_signal.emit("查重结果已写入 check_files.txt 文件中。")
        self.output_signal.emit("执行字符段匹配检测完成，输出结果为雷同文件、雷同片段统计！")

    def read_file(self, file_path):
        """
        读取文件内容，处理编码问题
        首先尝试使用utf-8编码读取文件，如果失败则使用gbk编码读取
        这样可以处理文件编码不一致的问题

        参数:
        file_path (str): 文件路径

        返回:
        str: 文件内容
        """
        # 尝试使用utf-8编码读取文件
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                return file.read()
        except UnicodeDecodeError:
            # 如果utf-8编码读取失败，则使用gbk编码读取
            with open(file_path, 'r', encoding='gbk') as file:
                return file.read()

    def is_valid_segment_start(self, content, index):
        """
        检查给定索引前面的字符是否符合条件
        
        此函数用于判断在给定内容和索引位置下，索引前面的字符是否属于一组特定的字符集合，
        以确定是否可以在这个位置开始一个新的代码段或标记。
        
        参数:
        - content: 字符串，代表要检查的内容。
        - index: 整数，代表要检查的位置索引。
        
        返回值:
        - 布尔值，如果索引位置前面的字符符合条件，则返回True，否则返回False。
        """
        if index == 0:
            return True  # 开头的字符没有前置字符，视为有效
        
        prev_char = content[index - 1]
        # 定义有效的前置字符集合
        valid_chars = set("_\n\r\t -*/=<>!&|({[]}).:")
        return prev_char in valid_chars

    def find_duplicates(self, name_dir, min_num, excl):
        """查找文件中的重复代码段
        
        该函数通过比较文件内容来识别重复的代码段。它不会比较文件自身，也不会将空白行或注释视为重复代码。
        参数:
        - name_dir: 包含文件名和内容的字典。文件名是键，内容是值。
        - min_num: 被认为是重复的最小代码段长度。
        - excl: 包含不应被视为重复的关键词列表，例如变量名或函数名。
        
        返回值:
        - duplicates: 包含重复代码段信息的字典，每个文件及其重复的代码段。
        """
        # 初始化一个空字典来存储重复代码段的信息
        duplicates = {}
        # 将文件名和内容的字典转换为列表，以便于迭代
        lst = list(name_dir.items())

        # 遍历每个文件，寻找重复的代码段
        for target_filename, target_content in lst:
            # 为当前文件初始化重复代码段的信息结构
            duplicates[target_filename] = {'duplicates': {}}

            # 将每个文件与其他文件进行比较
            for check_filename, check_content in lst:
                # 确保不比较同一个文件
                if target_filename != check_filename:
                    i = 0
                    # 遍历目标文件的内容，寻找重复的代码段
                    while i < len(target_content) - min_num + 1:
                        # 跳过空白行
                        if not target_content[i].isspace():
                            # 检查当前行是否是有效的代码段开始
                            if self.is_valid_segment_start(target_content, i):
                                # 提取当前考虑的代码段
                                code_segment = target_content[i:i + min_num]
                                # 确保代码段不包含任何排除的关键词，并且只包含字母、数字、空格和基本运算符
                                if re.fullmatch(r'[a-zA-Z0-9\s+\-*/=<>!&|]+', code_segment) and not any(keyword in code_segment for keyword in excl):
                                    # 如果代码段在其他文件中找到，则记录它
                                    if code_segment in check_content:
                                        # 避免重复记录相同的代码段
                                        if check_filename not in duplicates[target_filename]['duplicates']:
                                            duplicates[target_filename]['duplicates'][check_filename] = []
                                        if code_segment not in duplicates[target_filename]['duplicates'][check_filename]:
                                            duplicates[target_filename]['duplicates'][check_filename].append(code_segment)
                            # 移动到下一行
                            i += 1
                        else:
                            # 如果当前行是空白行，直接跳过
                            i += 1

        # 返回包含所有重复代码段信息的字典
        return duplicates

    def remove_comments_and_docstrings(self, source):
        """
        移除Python源代码中的注释和文档字符串。

        本函数的目的是接收一段Python源代码，然后移除其中的所有注释和文档字符串，
        返回清理后的源代码。这对于某些类型的代码分析或处理是有用的。

        参数:
        - source (str): 需要处理的Python源代码字符串。

        返回:
        - str: 移除注释和文档字符串后的源代码字符串。
        """
        # 使用StringIO代替文件操作，避免实际文件读写
        io_obj = StringIO(source)
        out = ""
        prev_toktype = tokenize.INDENT
        last_lineno = -1
        last_col = 0
        # 遍历代码中的每一个token
        for tok in tokenize.generate_tokens(io_obj.readline):
            token_type = tok[0]
            start_line, start_col = tok[2]
            end_line, end_col = tok[3]
            # 处理换行时的列号重置
            if start_line > last_lineno:
                last_col = 0
            # 处理行内空格
            if start_col > last_col:
                out += (" " * (start_col - last_col))
            # 忽略注释
            if token_type == tokenize.COMMENT:
                pass
            # 忽略缩进后的字符串，通常是文档字符串
            elif token_type == tokenize.STRING and prev_toktype == tokenize.INDENT:
                pass
            else:
                out += tok[1]
            prev_toktype = token_type
            last_col = end_col
            last_lineno = end_line
        # 移除每行末尾的空白字符
        out = '\n'.join([line.rstrip() for line in out.split('\n')])
        return out

    def calculate_code_similarity(self, code1, code2):
        """
        计算两段代码的相似度

        本函数通过移除空行并使用difflib库来比较两段代码的相似度
        相似度是一个介于0到1之间的浮点数，1表示完全相同，0表示完全不同

        参数:
        code1: 第一段代码，作为字符串输入
        code2: 第二段代码，作为字符串输入

        返回:
        代码相似度的浮点数表示
        """
        # 移除第一段代码中的空行
        code1_cleaned = self.remove_empty_lines(code1)
        # 移除第二段代码中的空行
        code2_cleaned = self.remove_empty_lines(code2)
        # 创建SequenceMatcher对象用于比较两段代码
        seq = difflib.SequenceMatcher(None, code1_cleaned, code2_cleaned)
        # 返回两段代码的相似度
        return seq.ratio()

    def remove_empty_lines(self, code):
        """
        移除代码中的空行。
        
        该方法接受一段代码作为输入，然后移除其中的所有空行。这对于清理代码或在保存代码到数据库等场景下非常有用。
        
        参数:
        code (str): 需要处理的代码字符串。
        
        返回:
        str: 移除空行后的代码字符串。
        """
        # 使用列表推导式，将非空行筛选出来，然后重新连接为一个字符串
        return '\n'.join(line for line in code.splitlines() if line.strip())

    def extract_similar_code(self, code1, code2):
        """
        提取两个代码片段中相似的代码行。

        该方法通过比较两段代码的差异，提取出在两段代码中都存在的非空白行代码。
        
        参数:
        code1 (str): 第一个代码片段。
        code2 (str): 第二个代码片段。
        
        返回:
        str: 两段代码中相似的代码行组成的字符串。
        """
        # 使用difflib.ndiff比较两段代码的差异
        diff = difflib.ndiff(code1.splitlines(), code2.splitlines())
        # 筛选出在两段代码中都存在的非空白行
        similar_code = [line for line in diff if line.startswith('  ') and line.strip()]
        # 将相似的代码行合并成一个字符串返回
        return '\n'.join(similar_code)

    def extract_variable_names(self, source):
        """
        提取并返回给定代码源中的所有变量名。
        
        参数:
        - source: 字符串形式的代码源。
        
        返回:
        - 一个包含所有变量名的集合。
        """
        # 解析源代码，生成AST（抽象语法树）
        tree = ast.parse(source)
        variable_names = set()
        
        class VariableVisitor(ast.NodeVisitor):
            """
            变量访问者类，用于遍历AST并提取变量名。
            """
            def visit_Name(self, node):
                """
                当遍历到一个Name节点时，检查其上下文并提取变量名。
                
                参数:
                - node: 当前遍历的AST节点。
                """
                # 如果Name节点的上下文是存储（赋值操作），则将其id（变量名）添加到集合中
                if isinstance(node.ctx, ast.Store):  
                    variable_names.add(node.id)
                # 继续遍历节点的子节点
                self.generic_visit(node)

        visitor = VariableVisitor()
        # 使用访问者遍历AST
        visitor.visit(tree)
        # 返回包含所有变量名的集合
        return variable_names

    def extract_function_and_class_names(self, source):
        """
        从给定的源代码字符串中提取函数和类的名称。
        
        参数:
        - source: 源代码字符串。
        
        返回:
        - function_names: 函数名称的集合。
        - class_names: 类名称的集合。
        """
        # 解析源代码为AST树
        tree = ast.parse(source)
        
        # 初始化函数名和类名的集合
        function_names = set()
        class_names = set()
        
        # 遍历AST树中的所有节点
        for node in ast.walk(tree):
            # 如果节点是一个函数定义，则将其名称添加到函数名集合中
            if isinstance(node, ast.FunctionDef):
                function_names.add(node.name)
            # 如果节点是一个类定义，则将其名称添加到类名集合中
            elif isinstance(node, ast.ClassDef):
                class_names.add(node.name)
        
        # 返回函数名和类名的集合
        return function_names, class_names

    def serialize_ast(self, source):
        """
        将源代码序列化为AST（抽象语法树）字符串表示。

        该方法主要用于将给定的源代码转换成相应的AST，以便于后续处理或分析。
        AST是源代码的树状结构表示，反映了源代码的语法结构，但不包含源代码中的具体数据。

        参数:
        source: str - 源代码字符串，将被解析为AST。

        返回:
        str - 源代码的AST序列化字符串表示，包含树的结构信息但不包含源代码中的具体数据。
        """
        # 解析源代码为AST
        tree = ast.parse(source)
        # 序列化AST为字符串，不包含属性值，但包含字段注释
        return ast.dump(tree, annotate_fields=True, include_attributes=False)

    def calculate_similarity(self, files):
        """
        计算文件列表中的文件相似度。

        该函数通过使用TF-IDF（Term Frequency-Inverse Document Frequency）向量化方法将文件转换为向量，
        然后计算这些向量之间的余弦相似度。

        参数:
        files (list of str): 包含多个文本文件内容的列表。每个文件内容作为一个字符串元素。

        返回:
        numpy.ndarray: 一个二维数组，表示文件之间的余弦相似度矩阵。
        """

        # 使用TfidfVectorizer将文件列表转换为TF-IDF向量矩阵
        vectorizer = TfidfVectorizer().fit_transform(files)
        
        # 将转换后的稀疏矩阵转为密集矩阵表示
        vectors = vectorizer.toarray()
        
        # 计算向量之间的余弦相似度
        cosine_sim = cosine_similarity(vectors)
        
        # 返回计算得到的余弦相似度矩阵
        return cosine_sim

    def visualize_similarity_matrix(self, df, output_path):
        """
        可视化代码结构相似度矩阵并保存图像。

        该函数接受一个包含相似度数据的DataFrame对象和一个输出路径作为参数，
        使用Seaborn库生成热图来可视化这些相似度数据，并将生成的图像保存到指定路径。

        参数:
        - df: DataFrame对象，包含代码结构相似度的数据。
        - output_path: 字符串，保存生成图像的路径。

        返回:
        无返回值。
        """
        # 创建一个新的图像，设置图像大小为10x8英寸，以提供足够的空间来清晰地显示相似度矩阵。
        plt.figure(figsize=(10, 8))
        
        # 使用Seaborn库的heatmap函数生成热图，显示代码结构相似度矩阵。
        # annot=True表示在热图中显示具体数值，fmt=".3f"指定数值的格式为保留三位小数的浮点数，
        # cmap='coolwarm'设置颜色映射为coolwarm，以提供更好的视觉对比度。
        sns.heatmap(df, annot=True, fmt=".3f", cmap='coolwarm')  
        
        # 设置图像标题为'代码结构相似度热图'，以描述图像内容。
        plt.title('代码结构相似度热图')
        
        # 将生成的图像保存到指定的输出路径，并关闭图像以释放资源。
        plt.savefig(output_path)
        plt.close()

class App(QWidget):
    """
    App类继承自QWidget，是应用程序的主要窗口类。
    
    该类负责初始化应用程序的用户界面，并包含应用程序运行所需的方法和属性。
    通过继承QWidget，App类可以获得基本的窗口功能，如显示窗口、处理用户输入等。
    """
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.worker = None  # 初始化 worker 变量

    def init_ui(self):
        self.setWindowTitle("Python文件重复度检测工具 Author:luwei")
        self.setGeometry(100, 100, 550, 400)  # 调整窗口大小

        layout = QVBoxLayout()

        self.label = QLabel("请选择测试数据集的文件夹路径")
        self.label.setStyleSheet("font-size: 16px; font-weight: bold;")
        layout.addWidget(self.label)

        self.folder_path_edit = QTextEdit()
        self.folder_path_edit.setReadOnly(True)
        self.folder_path_edit.setFixedHeight(30)
        layout.addWidget(self.folder_path_edit)

        # 使用 GridLayout 来更好地组织按钮及其说明文本
        grid_layout = QGridLayout()

        # 按钮和说明文本
        self.btn_browse = QPushButton("选择文件夹")
        self.btn_browse.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
        self.btn_browse.clicked.connect(self.browse_folder)
        grid_layout.addWidget(self.btn_browse, 0, 0)

        self.browse_info = QLabel("选择包含多个 Python 文件的文件夹")
        self.browse_info.setStyleSheet("font-size: 14px; color: #555555;")
        grid_layout.addWidget(self.browse_info, 0, 1)

        self.btn_open_output_folder = QPushButton("打开输出文件夹")
        self.btn_open_output_folder.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold;")
        self.btn_open_output_folder.clicked.connect(self.open_output_folder)
        grid_layout.addWidget(self.btn_open_output_folder, 1, 0)

        self.output_info = QLabel("查看执行结果和输出文件")
        self.output_info.setStyleSheet("font-size: 14px; color: #555555;")
        grid_layout.addWidget(self.output_info, 1, 1)

        self.btn_run_similarity = QPushButton("执行相似度检测")
        self.btn_run_similarity.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold;")
        self.btn_run_similarity.clicked.connect(lambda: self.run_scripts("similarity"))
        grid_layout.addWidget(self.btn_run_similarity, 2, 0)

        self.similarity_info = QLabel("检测文件间的相似度，输出相似度结果和重复变量、函数、类等信息")
        self.similarity_info.setStyleSheet("font-size: 14px; color: #555555;")
        grid_layout.addWidget(self.similarity_info, 2, 1)

        self.btn_run_duplication = QPushButton("执行重复度检测")
        self.btn_run_duplication.setStyleSheet("background-color: #FF5722; color: white; font-weight: bold;")
        self.btn_run_duplication.clicked.connect(lambda: self.run_scripts("duplication"))
        grid_layout.addWidget(self.btn_run_duplication, 3, 0)

        self.duplication_info = QLabel("检测文件中的重复代码并输出重复率和相似片段")
        self.duplication_info.setStyleSheet("font-size: 14px; color: #555555;")
        grid_layout.addWidget(self.duplication_info, 3, 1)

        self.btn_run_segment_duplication = QPushButton("执行字符段重复匹配")
        self.btn_run_segment_duplication.setStyleSheet("background-color: #9C27B0; color: white; font-weight: bold;")
        self.btn_run_segment_duplication.clicked.connect(self.run_segment_duplication)
        grid_layout.addWidget(self.btn_run_segment_duplication, 4, 0)

        self.segment_info = QLabel("检测代码段间的重复情况，输出雷同片段信息")
        self.segment_info.setStyleSheet("font-size: 14px; color: #555555;")
        grid_layout.addWidget(self.segment_info, 4, 1)

        layout.addLayout(grid_layout)

        # 创建输出框区域
        self.output_group_box = QGroupBox("程序运行进程提示:")
        output_layout = QVBoxLayout()

        self.output_edit = QTextEdit()
        self.output_edit.setReadOnly(True)
        self.output_edit.setStyleSheet("font-size: 15px;")
        #self.output_edit.setMaximumHeight(150)  # 设置最大高度
        output_layout.addWidget(self.output_edit)

        self.output_group_box.setLayout(output_layout)
        layout.addWidget(self.output_group_box)

        self.setLayout(layout)

    def browse_folder(self):
        folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹")
        if folder_path:
            self.folder_path_edit.setPlainText(folder_path)

    def run_scripts(self, selected_function):
        folder_path = self.folder_path_edit.toPlainText()
        if not folder_path:
            self.output_edit.setPlainText("请先选择文件夹路径。")
            return

        if self.worker and self.worker.isRunning():
            reply = QMessageBox.question(self, "确认", "当前检测正在进行，是否要中断并开始新的检测？",
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.No:
                return
            self.worker.stop()  
            self.worker.wait()  # 等待线程结束

        self.worker = Worker(folder_path, selected_function)
        self.worker.output_signal.connect(self.update_output)
        self.worker.start()

    def run_segment_duplication(self):
        folder_path = self.folder_path_edit.toPlainText()
        if not folder_path:
            self.output_edit.setPlainText("请先选择文件夹路径。")
            return

        if self.worker and self.worker.isRunning():
            reply = QMessageBox.question(self, "确认", "当前检测正在进行，是否要中断并开始新的检测？",
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.No:
                return
            self.worker.stop()  
            self.worker.wait()  # 等待线程结束

        self.worker = Worker(folder_path, "segment_duplication")
        self.worker.output_signal.connect(self.update_output)
        self.worker.start()

    def update_output(self, message):
        self.output_edit.append(message)

    def open_output_folder(self):
        folder_path = self.folder_path_edit.toPlainText()
        output_folder = os.path.join(folder_path, "输出结果")

        if os.path.exists(output_folder):
            os.startfile(output_folder)
        else:
            QMessageBox.warning(self, "警告", "请先执行查重程序或相似度检测。")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = App()
    window.show()
    sys.exit(app.exec())